#!/bin/bash

# AUTHOR:	(c)
# VERSION:	1.0
# LICENSE:	GPL (http://www.gnu.org/licenses/gpl.html)
# REQUIRES: petite chez scheme, sml of nj, binutils, diff
# NAME:		cgtest
# DESCRIPTION: code generator testing

# usage:
#	./cgtest [-d] <testfiles>...
#		tests runs compiler & petite on all tests files, one by one, and compares the result.
#	./cgtest [-d] -g <testfile>
#		generates code ($GENERATED_FILE_NAME) for the given test file (only one)
#	-d (debug) flag lets you see the running of the test.

# config these:
TEST_ROOT=`pwd`
TEST_TEMP_WORK_AREA="${TEST_ROOT}/tmp"
C_SOURCES="../src/c"
ML_SOURCES="../src/sml"
COMPILER_FILE="compiler.sml"
CG_OUTPUT_FILE="${TEST_TEMP_WORK_AREA}/${TEST_FILE}_cg_output.txt"
GENERATED_FILE_NAME="code"
CG_CCODE="${C_SOURCES}/${GENERATED_FILE_NAME}.c"
REAL_OUTPUT_FILE="${TEST_TEMP_WORK_AREA}/${TEST_FILE}_real_output.txt"
SML_TEST_RUNNER="${TEST_TEMP_WORK_AREA}/cgtest.sml"

FILE_ERROR_MSG="fail: no such file"
ML_ERROR_MSG="fail: ML compilation error"
C_ERROR_MSG="fail: C code does not compile or assertion failed"
CORRECTNESS_ERROR_MSG="fail: result is incorrect"
NO_ERROR_MSG="pass"
COMPARE_MODE=0

# ---------------- library ----------------

function printErrorStats
{
	case "${ERROR}" in
		"FILE")	RESULT=${FILE_ERROR_MSG} ;;
		"ML")	RESULT=${ML_ERROR_MSG} ;;
		"C")	RESULT=${C_ERROR_MSG} ;;
		"DIFF")	RESULT=${CORRECTNESS_ERROR_MSG} ;;
		"")		RESULT=${NO_ERROR_MSG} ;;
		*)		RESULT="shouldnt get here" ;;
	esac
	echo "${TEST_FILE}: ${RESULT}"
}

function mkFullPath
# assumes arg is a relative path from current dir
{
	_C_DIRNAME=`dirname "$1"`
	_C_BASENAME=`basename "$1"`
	_C_PATH="`cd \"${_C_DIRNAME}\" 2>/dev/null && pwd || echo \"${_C_DIRNAME}\"`/${_C_BASENAME}"

	echo ${_C_PATH}
}

function getCurrentTestFile
{
	if [ -f $1 ]
	then
		TEST_FILE=`mkFullPath $1`
	else
		if [ -e ${TEST_ROOT}/$1 ]
			then TEST_FILE="${TEST_ROOT}/$1"
			else ERROR="FILE"
		fi
	fi
}

# ---------------- testing layer 2 -----------------

function prepareWorkspace
{
	echo "preparing ${TEST_ROOT}"
	make CG_OUTPUT_FILE=${CG_OUTPUT_FILE} clean
	rm -Rf ${REAL_OUTPUT_FILE}
	rm -Rf ${CG_CCODE}
	rm -Rf ${CG_OUTPUT_FILE}
	rm -Rf ${SML_TEST_RUNNER}
	mkdir -p ${TEST_ROOT}
	mkdir -p ${TEST_TEMP_WORK_AREA}
	ERROR=""
	RESULT=""
}

function getRealSchemeResult
{
	echo "getting results from ${TEST_FILE} to ${REAL_OUTPUT_FILE}"
	petite -q < ${TEST_FILE} | tr -d "\t\r\n" > ${REAL_OUTPUT_FILE}
}

function makeSmlTestRunner
{
	echo "creating test runner ${SML_TEST_RUNNER}"
	echo "use \"${COMPILER_FILE}\" ;
use \"exitFromSml.sml\" ;
CodeGen.compileSchemeFile (\"${TEST_FILE}\", \"${C_SOURCE_FOR_SML}/${GENERATED_FILE_NAME}.c\");
exit ();
" > ${SML_TEST_RUNNER}
	echo "test runner:"
	cat ${SML_TEST_RUNNER}
}

function convertSchemeFileToC
{
	echo "generating c code"
	makeSmlTestRunner

	cd "${ML_SOURCES}"
	cat ${SML_TEST_RUNNER} | sml
	cd -

	if ! [ -f "${CG_CCODE}" ] && [ -z ${ERROR} ]
	then
		ERROR="ML"
	fi
}

function getGeneratedResult
{
	echo "creating tested result"
	convertSchemeFileToC
	make CG_OUTPUT_FILE=${CG_OUTPUT_FILE} GENERATED_CODE=${GENERATED_FILE_NAME} run
	if [ $? != 0 ] && [ -z ${ERROR} ]
		then ERROR="C"
	fi
	cat ${CG_OUTPUT_FILE} | tr -d "\t\r\n" > /tmp/cgtest_temporary.txt
	cp /tmp/cgtest_temporary.txt ${CG_OUTPUT_FILE}
}

function compareResults
{
	RESULT=`diff -NwadqbB ${REAL_OUTPUT_FILE} ${CG_OUTPUT_FILE}`
	if [ -n "$RESULT" ] && [ -z ${ERROR} ]
		then ERROR="DIFF"
 	fi
}

# ---------------- testing layer 1 ----------------

function initialize
{
	ERROR=""
	C_SOURCE_FOR_SML=`mkFullPath ${C_SOURCES}`
	DEBUG_OUTPUT="/dev/null"
	GENERATE_ONLY=0
}

function generateTestFileCCode
{
	prepareWorkspace > ${DEBUG_OUTPUT} 2> ${DEBUG_OUTPUT}
	convertSchemeFileToC > ${DEBUG_OUTPUT} 2> ${DEBUG_OUTPUT}
	printErrorStats
}

function testOneTestFileAgainstScheme
{
	prepareWorkspace > ${DEBUG_OUTPUT} 2> ${DEBUG_OUTPUT}
	getRealSchemeResult > ${DEBUG_OUTPUT} 2> ${DEBUG_OUTPUT}
	getGeneratedResult > ${DEBUG_OUTPUT} 2> ${DEBUG_OUTPUT}
	compareResults > ${DEBUG_OUTPUT} 2> ${DEBUG_OUTPUT}
	printErrorStats
}

# ---------------- main ----------------

initialize

#parse options, have to be done outside of a func

while [ $# -gt 0 ]
do
	case "$1" in
		-d) DEBUG_OUTPUT="/dev/stdout" ;;
		-g) GENERATE_ONLY=1 ;;
		-c) COMPARE_MODE=1 ;;
		*)  break;;
	esac

	shift
done

if [ ${GENERATE_ONLY} != 0 ]
then
	getCurrentTestFile $1
	generateTestFileCCode
else
	until [ $# = 0 ]
	do
		getCurrentTestFile $1
		testOneTestFileAgainstScheme
		
		if [ ${COMPARE_MODE} != 0 ] 
		then
			echo -e "\n=== original code ==="
			cat ${TEST_FILE}
			echo -e "\n=== generated output ==="
			cat ${CG_OUTPUT_FILE}
			echo -e "=== petite output ==="
			cat ${REAL_OUTPUT_FILE}
		fi
		
		shift
	done
	
	
	
fi

exit
